﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using VFNStockTrader.BSL.MarketDataFeed.DataContracts;
using System.Threading;
using XchangeStreamer.OutputSdk.Internal;
using System.Collections.ObjectModel;

namespace XchangeStreamer.OutputSdk.ViewModel
{
    [Serializable]
    public sealed class Symbol : INotifyPropertyChanged
    {
        #region Private Fields

        private string isin_code;
        private string reuters_code;
        private string eng_name;
        private string arb_name;
        private decimal open_price;
        private decimal close_price;
        private decimal high_price;
        private decimal low_price;
        private decimal change;
        private decimal avg_change;
        private decimal change_percent;
        private decimal avg_change_percent;
        private decimal price;
        private uint volume;
        private TimeSpan time;
        private DateTime date;
        private decimal value;
        private uint cum_volume;
        private decimal cum_value;
        private uint no_of_trades;
        private decimal best_bid_price;
        private decimal best_ask_price;
        private uint best_bid_volume;
        private uint best_ask_volume;
        private decimal total_bid_value;
        private decimal total_ask_value;
        private uint total_bid_volume;
        private uint total_ask_volume;
        private uint no_of_bids;
        private uint no_of_asks;
        private decimal bids_asks_ratio;
        private decimal previous_close_price;

        #region Internal Stores

        private ObservableCollection<Order> marketDepthByOrderBids;
        private ObservableCollection<Order> marketDepthByOrderAsks;
        private MarketDepth<Order> readonlyMarketDepthByOrder;

        private ObservableCollection<OrderBookRecord> marketDepthByPriceBids;
        private ObservableCollection<OrderBookRecord> marketDepthByPriceAsks;
        private MarketDepth<OrderBookRecord> readonlyMarketDepthByPrice;

        private ObservableCollection<Trade> trades;
        private IList<Trade> readonlyTrades;

        private ObservableCollection<RawMainIndex> indexes;
        private IList<RawMainIndex> readonlyIndexes;

        private ObservableCollection<RawSymbolInfo> symbolsInfo;
        private IList<RawSymbolInfo> readonlySymbolsInfo;

        private ObservableCollection<RawSymbolSummary> symbolsSummary;
        private IList<RawSymbolSummary> readonlySymbolsSummary;

        private ObservableCollection<MarketBasicInfo> basicInfo;
        private IList<MarketBasicInfo> readonlyBasicInfo;

        private ObservableCollection<RawSector> sectors;
        private IList<RawSector> readonlySectors;

        private ObservableCollection<RawSymbol52HighLow> symbols52HighLow;
        private IList<RawSymbol52HighLow> readonlySymbols52HighLow;

        private ObservableCollection<RawBulletin> bulletins;
        private IList<RawBulletin> readonlyBulletins;

        private ObservableCollection<RawNews> news;
        private IList<RawNews> readonlyNews;

        private ObservableCollection<RawCaseSectorIndex> sectorsIndex;
        private IList<RawCaseSectorIndex> readonlySectorsIndex;

        private ObservableCollection<RawInvestor> investors;
        private IList<RawInvestor> readonlyInvestors;

        private ObservableCollection<RawInstitationsAndReatailRatios> retialRatios;
        private IList<RawInstitationsAndReatailRatios> readonlyRetialRatios;

        #endregion

        #endregion

        #region Public Constructors

        public Symbol()
        {
            this.marketDepthByOrderBids = new ObservableCollection<Order>();
            this.marketDepthByOrderAsks = new ObservableCollection<Order>();
            this.readonlyMarketDepthByOrder = new MarketDepth<Order>(this.marketDepthByOrderBids, this.marketDepthByOrderAsks);

            this.marketDepthByPriceBids = new ObservableCollection<OrderBookRecord>();
            this.marketDepthByPriceAsks = new ObservableCollection<OrderBookRecord>();
            this.readonlyMarketDepthByPrice = new MarketDepth<OrderBookRecord>(this.marketDepthByPriceBids, this.marketDepthByPriceAsks);

            this.trades = new ObservableCollection<Trade>();
            this.indexes = new ObservableCollection<RawMainIndex>();
            this.symbolsInfo = new ObservableCollection<RawSymbolInfo>();
            this.symbolsSummary = new ObservableCollection<RawSymbolSummary>();
            this.basicInfo = new ObservableCollection<MarketBasicInfo>();
            this.sectors = new ObservableCollection<RawSector>();
            this.symbols52HighLow = new ObservableCollection<RawSymbol52HighLow>();
            this.bulletins = new ObservableCollection<RawBulletin>();
            this.news = new ObservableCollection<RawNews>();
            this.sectorsIndex = new ObservableCollection<RawCaseSectorIndex>();
            this.investors = new ObservableCollection<RawInvestor>();
            this.retialRatios = new ObservableCollection<RawInstitationsAndReatailRatios>();

            this.trades.Add(new Trade("EGS74081C018", "", 0, 1, TimeSpan.FromSeconds(50)));
            this.marketDepthByPriceBids.Add(new OrderBookRecord(0, 1, 1, 0));
            this.marketDepthByPriceAsks.Add(new OrderBookRecord(0, 1, 1, 0));

            this.readonlyTrades = Factory.Create(this.trades);
            this.readonlyIndexes = Factory.Create(this.indexes);
            this.readonlySymbolsInfo = Factory.Create(this.symbolsInfo);
            this.readonlySymbolsSummary = Factory.Create(this.symbolsSummary);
            this.readonlyBasicInfo = Factory.Create(this.basicInfo);
            this.readonlySectors = Factory.Create(this.sectors);
            this.readonlySymbols52HighLow = Factory.Create(this.symbols52HighLow);
            this.readonlyBulletins = Factory.Create(this.bulletins);
            this.readonlyNews = Factory.Create(this.news);
            this.readonlySectorsIndex = Factory.Create(this.sectorsIndex);
            this.readonlyInvestors = Factory.Create(this.investors);
            this.readonlyRetialRatios = Factory.Create(this.retialRatios);
        }

        #endregion

        #region Public Properties

        public string Isin_Code
        {
            get
            {
                return this.isin_code;
            }
            set
            {
                if (this.isin_code != value)
                {
                    this.isin_code = value;
                    this.InvokePropertyChanged("Isin_Code");
                }
            }
        }

        public string Reuters_Code
        {
            get
            {
                return this.reuters_code;
            }
            set
            {
                if (this.reuters_code != value)
                {
                    this.reuters_code = value;
                    this.InvokePropertyChanged("Reuters_Code");
                }
            }
        }

        public string Eng_Name
        {
            get
            {
                return this.eng_name;
            }
            set
            {
                if (this.eng_name != value)
                {
                    this.eng_name = value;
                    this.InvokePropertyChanged("Eng_Name");
                }
            }
        }

        public string Arb_Name
        {
            get
            {
                return this.arb_name;
            }
            set
            {
                if (this.arb_name != value)
                {
                    this.arb_name = value;
                    this.InvokePropertyChanged("Arb_Name");
                }
            }
        }

        public decimal Open_Price
        {
            get
            {
                return this.open_price;
            }
            set
            {
                if (this.open_price != value)
                {
                    this.open_price = value;
                    this.InvokePropertyChanged("Open_Price");
                }
            }
        }

        public decimal Close_Price
        {
            get
            {
                return this.close_price;
            }
            set
            {
                if (this.close_price != value)
                {
                    this.close_price = value;
                    this.InvokePropertyChanged("Close_Price");
                }
            }
        }

        public decimal High_Price
        {
            get
            {
                return this.high_price;
            }
            set
            {
                if (this.high_price != value)
                {
                    this.high_price = value;
                    this.InvokePropertyChanged("High_Price");
                }
            }
        }

        public decimal Low_Price
        {
            get
            {
                return this.low_price;
            }
            set
            {
                if (this.low_price != value)
                {
                    this.low_price = value;
                    this.InvokePropertyChanged("Low_Price");
                }
            }
        }

        public decimal Change
        {
            get
            {
                return this.change;
            }
            set
            {
                if (this.change != value)
                {
                    this.change = value;
                    this.InvokePropertyChanged("Change");
                }
            }
        }

        public decimal Avg_Change
        {
            get
            {
                return this.avg_change;
            }
            set
            {
                if (this.avg_change != value)
                {
                    this.avg_change = value;
                    this.InvokePropertyChanged("Avg_Change");
                }
            }
        }

        public decimal Change_Percent
        {
            get
            {
                return this.change_percent;
            }
            set
            {
                if (this.change_percent != value)
                {
                    this.change_percent = value;
                    this.InvokePropertyChanged("Change_Percent");
                }
            }
        }

        public decimal Avg_Change_Percent
        {
            get
            {
                return this.avg_change_percent;
            }
            set
            {
                if (this.avg_change_percent != value)
                {
                    this.avg_change_percent = value;
                    this.InvokePropertyChanged("Avg_Change_Percent");
                }
            }
        }

        public decimal Price
        {
            get
            {
                return this.price;
            }
            set
            {
                if (this.price != value)
                {
                    this.price = value;
                    this.InvokePropertyChanged("Price");
                }
            }
        }

        public uint Volume
        {
            get
            {
                return this.volume;
            }
            set
            {
                if (this.volume != value)
                {
                    this.volume = value;
                    this.InvokePropertyChanged("Volume");
                }
            }
        }

        public TimeSpan Time
        {
            get
            {
                return this.time;
            }
            set
            {
                if (this.time != value)
                {
                    this.time = value;
                    this.InvokePropertyChanged("Time");
                }
            }
        }

        public DateTime Date
        {
            get
            {
                return this.date;
            }
            set
            {
                if (this.date != value)
                {
                    this.date = value;
                    this.InvokePropertyChanged("Date");
                }
            }
        }

        public decimal Value
        {
            get
            {
                return this.value;
            }
            set
            {
                if (this.value != value)
                {
                    this.value = value;
                    this.InvokePropertyChanged("Value");
                }
            }
        }

        public uint Cum_Volume
        {
            get
            {
                return this.cum_volume;
            }
            set
            {
                if (this.cum_volume != value)
                {
                    this.cum_volume = value;
                    this.InvokePropertyChanged("Cum_Volume");
                }
            }
        }

        public decimal Cum_Value
        {
            get
            {
                return this.cum_value;
            }
            set
            {
                if (this.cum_value != value)
                {
                    this.cum_value = value;
                    this.InvokePropertyChanged("Cum_Value");
                }
            }
        }

        public uint No_Of_Trades
        {
            get
            {
                return this.no_of_trades;
            }
            set
            {
                if (this.no_of_trades != value)
                {
                    this.no_of_trades = value;
                    this.InvokePropertyChanged("No_Of_Trades");
                }
            }
        }

        public decimal Best_Bid_Price
        {
            get
            {
                return this.best_bid_price;
            }
            set
            {
                if (this.best_bid_price != value)
                {
                    this.best_bid_price = value;
                    this.InvokePropertyChanged("Best_Bid_Price");
                }
            }
        }

        public decimal Best_Ask_Price
        {
            get
            {
                return this.best_ask_price;
            }
            set
            {
                if (this.best_ask_price != value)
                {
                    this.best_ask_price = value;
                    this.InvokePropertyChanged("Best_Ask_Price");
                }
            }
        }

        public uint Best_Bid_Volume
        {
            get
            {
                return this.best_bid_volume;
            }
            set
            {
                if (this.best_bid_volume != value)
                {
                    this.best_bid_volume = value;
                    this.InvokePropertyChanged("Best_Bid_Volume");
                }
            }
        }

        public uint Best_Ask_Volume
        {
            get
            {
                return this.best_ask_volume;
            }
            set
            {
                if (this.best_ask_volume != value)
                {
                    this.best_ask_volume = value;
                    this.InvokePropertyChanged("Best_Ask_Volume");
                }
            }
        }

        public decimal Total_Bid_Value
        {
            get
            {
                return this.total_bid_value;
            }
            set
            {
                if (this.total_bid_value != value)
                {
                    this.total_bid_value = value;
                    this.InvokePropertyChanged("Total_Bid_Value");
                }
            }
        }

        public decimal Total_Ask_Value
        {
            get
            {
                return this.total_ask_value;
            }
            set
            {
                if (this.total_ask_value != value)
                {
                    this.total_ask_value = value;
                    this.InvokePropertyChanged("Total_Ask_Value");
                }
            }
        }

        public uint Total_Bid_Volume
        {
            get
            {
                return this.total_bid_volume;
            }
            set
            {
                if (this.total_bid_volume != value)
                {
                    this.total_bid_volume = value;
                    this.InvokePropertyChanged("Total_Bid_Volume");
                }
            }
        }

        public uint Total_Ask_Volume
        {
            get
            {
                return this.total_ask_volume;
            }
            set
            {
                if (this.total_ask_volume != value)
                {
                    this.total_ask_volume = value;
                    this.InvokePropertyChanged("Total_Ask_Volume");
                }
            }
        }

        public uint No_Of_Bids
        {
            get
            {
                return this.no_of_bids;
            }
            set
            {
                if (this.no_of_bids != value)
                {
                    this.no_of_bids = value;
                    this.InvokePropertyChanged("No_Of_Bids");
                }
            }
        }

        public uint No_Of_Asks
        {
            get
            {
                return this.no_of_asks;
            }
            set
            {
                if (this.no_of_asks != value)
                {
                    this.no_of_asks = value;
                    this.InvokePropertyChanged("No_Of_Asks");
                }
            }
        }

        public decimal Bids_Asks_Ratio
        {
            get
            {
                return this.bids_asks_ratio;
            }
            set
            {
                if (this.bids_asks_ratio != value)
                {
                    this.bids_asks_ratio = value;
                    this.InvokePropertyChanged("Bids_Asks_Ratio");
                }
            }
        }

        public decimal Previous_Close_Price
        {
            get
            {
                return this.previous_close_price;
            }
            set
            {
                if (this.previous_close_price != value)
                {
                    this.previous_close_price = value;
                    this.InvokePropertyChanged("Previous_Close_Price");
                }
            }
        }

        public IList<Order> MarketDepthByOrder_Bids
        {
            get
            {
                return this.readonlyMarketDepthByOrder.Bids;
            }
        }

        public IList<Order> MarketDepthByOrder_Asks
        {
            get
            {
                return this.readonlyMarketDepthByOrder.Asks;
            }
        }

        public IList<OrderBookRecord> MarketDepthByPrice_Bids
        {
            get
            {
                return this.readonlyMarketDepthByPrice.Bids;
            }
        }

        public IList<OrderBookRecord> MarketDepthByPrice_Asks
        {
            get
            {
                return this.readonlyMarketDepthByPrice.Asks;
            }
        }

        public IList<Trade> Trades
        {
            get
            {
                return this.readonlyTrades;
            }
        }

        public IList<RawMainIndex> Indexes
        {
            get
            {
                return this.readonlyIndexes;
            }
        }

        public IList<RawSymbolInfo> SymbolsInfo
        {
            get
            {
                return this.readonlySymbolsInfo;
            }
        }

        public IList<RawSymbolSummary> SymbolsSummary
        {
            get
            {
                return this.readonlySymbolsSummary;
            }
        }

        public IList<MarketBasicInfo> BasicInfo
        {
            get
            {
                return this.readonlyBasicInfo;
            }
        }

        public IList<RawSector> Sectors
        {
            get
            {
                return this.readonlySectors;
            }
        }

        public IList<RawSymbol52HighLow> Symbols52HighLow
        {
            get
            {
                return this.readonlySymbols52HighLow;
            }
        }

        public IList<RawBulletin> Bulletins
        {
            get
            {
                return this.readonlyBulletins;
            }
        }

        public IList<RawNews> News
        {
            get
            {
                return this.readonlyNews;
            }
        }

        public IList<RawCaseSectorIndex> SectorsIndex
        {
            get
            {
                return this.readonlySectorsIndex;
            }
        }

        public IList<RawInvestor> Investors
        {
            get
            {
                return this.readonlyInvestors;
            }
        }

        public IList<RawInstitationsAndReatailRatios> RetialRatios
        {
            get
            {
                return this.readonlyRetialRatios;
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        private void InvokePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                try
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
                catch { }
            }
        }

        #endregion

        #region Internal Methods

        internal void Process(IEnumerable<RawOrder> orders, QuoteType type)
        {
            if (orders == null)
            {
                return;
            }

            switch (type)
            {
                case QuoteType.Buy:
                    this.Process(this.marketDepthByOrderBids, this.marketDepthByPriceBids, orders);
                    break;
                case QuoteType.Sell:
                    this.Process(this.marketDepthByOrderAsks, this.marketDepthByPriceAsks, orders);
                    break;
                default:
                    break;
            }
        }

        internal void Process(IEnumerable<RawTrade> trades)
        {
            foreach (var newTrade in trades)
            {
                this.trades.Add(new Trade(newTrade.SymbolCode, newTrade.TicketNumber, newTrade.Price, newTrade.Volume, newTrade.Time));
            }
        }

        internal void Process(RawSymbolSummary rawSymbolSummary)
        {
            //throw new NotImplementedException();
        }

        internal void Process(RawSymbol52HighLow rawSymbol52HighLow)
        {
            //throw new NotImplementedException();
        }

        internal void Process(RawBulletin rawBulletin)
        {
            //throw new NotImplementedException();
        }

        internal void Process(RawNews rawNews)
        {
            //throw new NotImplementedException();
        }

        #endregion

        #region Private Methods

        private void Process(ICollection<Order> marketDepthByOrder, ICollection<OrderBookRecord> marketDepthByPrice, IEnumerable<RawOrder> newOrders)
        {
            foreach (var newOrder in newOrders)
            {
                var order = marketDepthByOrder.FirstOrDefault(o => o.OrderNumber == newOrder.OrderNumber);
                if (newOrder.OrderAction == QuoteStatus.Open)
                {
                    if (order == null)
                    {
                        marketDepthByOrder.Add(new Order(newOrder.SymbolCode, newOrder.OrderNumber, newOrder.Price, newOrder.Volume, newOrder.Time));

                        var bookRecord = marketDepthByPrice.FirstOrDefault(b => b.Price == newOrder.Price);
                        if (bookRecord == null)
                        {
                            marketDepthByPrice.Add(new OrderBookRecord(newOrder.Price, newOrder.Volume, 1, newOrder.Price * newOrder.Volume));
                        }
                        else
                        {
                            bookRecord.Quantity += newOrder.Volume;
                            bookRecord.TurnOver += (newOrder.Price * newOrder.Volume);
                            bookRecord.Splits++;
                        }
                    }
                    else
                    {
                        if (order.Price == newOrder.Price)
                        {
                            var bookRecord = marketDepthByPrice.FirstOrDefault(b => b.Price == newOrder.Price);
                            if (bookRecord == null)
                            {
                                marketDepthByPrice.Add(new OrderBookRecord(newOrder.Price, newOrder.Volume, 1, newOrder.Price * newOrder.Volume));
                            }
                            else
                            {
                                bookRecord.Quantity -= order.Quantity;
                                bookRecord.Quantity += newOrder.Volume;

                                bookRecord.TurnOver -= (newOrder.Price * order.Quantity);
                                bookRecord.TurnOver += (newOrder.Price * newOrder.Volume);
                            }
                        }
                        else
                        {
                            var oldBookRecord = marketDepthByPrice.FirstOrDefault(b => b.Price == order.Price);
                            if (oldBookRecord != null)
                            {
                                oldBookRecord.Quantity -= order.Quantity;
                                oldBookRecord.TurnOver -= (order.Price * order.Quantity);
                                oldBookRecord.Splits--;

                                if (oldBookRecord.Splits < 1 || oldBookRecord.Quantity < 1 || oldBookRecord.TurnOver <= 0)
                                {
                                    marketDepthByPrice.Remove(oldBookRecord);
                                }
                            }

                            var newBookRecord = marketDepthByPrice.FirstOrDefault(b => b.Price == newOrder.Price);
                            if (newBookRecord == null)
                            {
                                marketDepthByPrice.Add(new OrderBookRecord(newOrder.Price, newOrder.Volume, 1, newOrder.Price * newOrder.Volume));
                            }
                            else
                            {
                                newBookRecord.Quantity += newOrder.Volume;
                                newBookRecord.TurnOver += (newOrder.Price * newOrder.Volume);
                                newBookRecord.Splits++;
                            }
                        }

                        order.Price = newOrder.Price;
                        order.Quantity = newOrder.Volume;
                        order.Time = newOrder.Time;
                    }
                }
                else
                {
                    if (order != null)
                    {
                        var bookRecord = marketDepthByPrice.FirstOrDefault(b => b.Price == order.Price);
                        if (bookRecord != null)
                        {
                            bookRecord.Quantity -= order.Quantity;
                            bookRecord.TurnOver -= (order.Price * order.Quantity);
                            bookRecord.Splits--;

                            if (bookRecord.Splits < 1 || bookRecord.Quantity < 1 || bookRecord.TurnOver <= 0)
                            {
                                marketDepthByPrice.Remove(bookRecord);
                            }
                        }

                        marketDepthByOrder.Remove(order);
                    }
                }
            }
        }

        #endregion

        public string GetValue(string propName)
        {
            switch (propName)
            {
                case "Isin_Code":
                    return this.isin_code;
                default:
                    return null;
            }
        }
    }
}